
/*    This file is part of the Managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanavalib.utils;

// import java util classes
import java.util.Iterator;
import java.util.HashMap;
import java.util.ArrayList;
import java.io.OutputStream;
import java.io.IOException;

// import managarm virtual architecture
import org.managarm.jmanavalib.core.VaClass;
import org.managarm.jmanavalib.core.VaDatatype;
import org.managarm.jmanavalib.core.VaClsInfo;
import org.managarm.jmanavalib.core.VaMthInfo;
import org.managarm.jmanavalib.core.VaFldInfo;
import org.managarm.jmanavalib.core.VaProcedure;
import org.managarm.jmanavalib.core.VaFrame;
import org.managarm.jmanavalib.core.VaInst;
import org.managarm.jmanavalib.core.VaLiteral;

public class VaWriter {
	private static final int CNST_NULL = 0;
	
	private static final int VAIMG_VERSION = 1;
	private static final int VAIMG_MODEL = 2;
	
	// constant type ids
	private static final short CNST_CLSINFO = 1;
	private static final short CNST_MTHINFO = 2;
	private static final short CNST_FLDINFO = 3;
	private static final short CNST_PRIMTYPE = 4;
	private static final short CNST_CLSTYPE = 5;
	private static final short CNST_ARRTYPE = 6;
	private static final short CNST_DLGTYPE = 10;
	private static final short CNST_CARR_LITERAL = 7;
	private static final short CNST_INT_LITERAL = 8;
	private static final short CNST_UINT_LITERAL = 9;
	private static final short CNST_CHAR_LITERAL = 20;
	private static final short CNST_LIT_BOOLEAN = 21;
	private static final short CNST_LIT_NULL = 22;
	
	// operation code constants
	private static final short OPCODE_RETURN = 1;
	private static final short OPCODE_LOAD = 2;
	private static final short OPCODE_INVOKE_VIRT_RES = 3;
	private static final short OPCODE_INVOKE_VIRT_VOID = 4;
	private static final short OPCODE_INVOKE_STAT_RES = 5;
	private static final short OPCODE_INVOKE_STAT_VOID = 6;
	private static final short OPCODE_BINOP_ARITH_ADD = 7;
	private static final short OPCODE_BINOP_ARITH_SUB = 8;
	private static final short OPCODE_BINOP_ARITH_MUL = 9;
	private static final short OPCODE_BINOP_ARITH_DIV = 10;
	private static final short OPCODE_BINOP_ARITH_MOD = 11;
	private static final short OPCODE_BINOP_ARITH_OR = 12;
	private static final short OPCODE_BINOP_ARITH_AND = 13;
	private static final short OPCODE_BINOP_ARITH_XOR = 14;
	private static final short OPCODE_BINOP_ARITH_SHL = 15;
	private static final short OPCODE_BINOP_ARITH_SHR = 16;
	private static final short OPCODE_BINOP_SETIF_EQUALS = 17;
	private static final short OPCODE_BINOP_SETIF_ABOVE = 18;
	private static final short OPCODE_BINOP_SETIF_BELOW = 19;
	private static final short OPCODE_UNOP_ARITH_NOT = 20;
	private static final short OPCODE_UNOP_ARITH_NEG = 21;
	private static final short OPCODE_UNOP_ARR_SIZE = 22;
	private static final short OPCODE_JUMP = 23;
	private static final short OPCODE_CREATE_OBJECT = 24;
	private static final short OPCODE_CREATE_ARRAY = 25;
	private static final short OPCODE_ARRACC_LOAD = 26;
	private static final short OPCODE_ARRACC_STORE = 27;
	private static final short OPCODE_FLDACC_LOAD = 28;
	private static final short OPCODE_FLDACC_STORE = 29;
	private static final short OPCODE_CAST = 30;
	private static final short OPCODE_INSTANCEOF = 31;
	private static final short OPCODE_DLG_CONSTRUCT = 32;
	private static final short OPCODE_CATCH = 33;
	private static final short OPCODE_THROW = 34;
	private static final short OPCODE_PASS = 35;
	private static final short OPCODE_LEAVE = 36;
	
	// instruction set extension constants
	private static final short OPCODE_EXT0 = 255;
	
	// first opcode extension
	private static final long EXT0_XNAT_METHOD_ADDRESS = 1;
	private static final long EXT0_XNAT_SIZEOF_OBJECT = 2;
	private static final long EXT0_XNAT_SIZEOF_ARRAY = 3;
	private static final long EXT0_XNAT_ADDRESS_CAST = 4;
	private static final long EXT0_XNAT_ADDRESS_OF = 5;
	private static final long EXT0_XNAT_MAKE_OBJECT = 6;
	private static final long EXT0_XNAT_MAKE_ARRAY = 7;
	private static final long EXT0_XNAT_MAKE_DELEGATE = 8;
	private static final long EXT0_XNAT_REF_INCREMENT = 9;
	private static final long EXT0_XNAT_REF_DECREMENT = 10;
	private static final long EXT0_XATOM_CAS_RESULT = 100;
	private static final long EXT0_XATOM_CAS_VOID = 101;
	private static final long EXT0_XPERF_ARRAY_COPY = 200;
	
	// constant maps
	private final HashMap<Object, Integer> cnstMap = new HashMap<Object, Integer>();
	private final HashMap<String, Integer> stringMap = new HashMap<String, Integer>();
	
	// constant arrays
	private final ArrayList<Object> cnstList = new ArrayList<Object>();
	private final ArrayList<String> stringList = new ArrayList<String>();
	
	private final VaImage image;
	private int classCnt = 0;
	private int methodCnt = 0;
	
	public VaWriter(VaImage image) throws ValibException {
		// save the image
		this.image = image;
		
		// analyze the classes
		final Iterator<VaClsInfo> clsiter = image.classIterator();
		while(clsiter.hasNext()) {
			final VaClsInfo clsinfo = clsiter.next();
			mergeConst(clsinfo);
			analyzeClass(image.getClass(clsinfo));
			classCnt++;
		}
		
		// analyze the methods
		final Iterator<VaMthInfo> seciter = image.methodIterator();
		while(seciter.hasNext()) {
			final VaMthInfo mthinfo = seciter.next();
			mergeConst(mthinfo);
			analyzeSection(image.getMethod(mthinfo));
			methodCnt++;
		}
	}
	
	// write the image to the stream
	public void write(OutputStream stream) throws ValibException {
		// write the header
		writeUInt(VAIMG_VERSION, stream);
		writeUInt(VAIMG_MODEL, stream);
		
		// write the string count
		writeUInt(stringList.size(), stream);
		
		// write the strings
		for(int i = 0; i < stringList.size(); i++) {
			final String string = stringList.get(i);
			
			// write the string length
			writeUInt(string.length(), stream);
			
			// write the chars
			for(int j = 0; j < string.length(); j++) {
				if(string.charAt(j) >= 0xFF) {
					writeUByte((short)0xFF, stream);
					writeUInt((long)string.charAt(j), stream);
				}else{
					writeUByte((short)string.charAt(j), stream);
				}
			}
		}
		
		// write the constant count
		writeUInt(cnstList.size(), stream);
		
		// write the constants
		for(int i = 0; i < cnstList.size(); i++) {
			final Object cnst = cnstList.get(i);
			
			if(cnst instanceof VaClsInfo) {
				final VaClsInfo info = (VaClsInfo)cnst;
				
				// write the constant's type
				writeUByte(CNST_CLSINFO, stream);
				
				// write the class' identifier and version
				writeUInt(stringMap.get(info.ident), stream);
				writeUInt(info.major, stream);
				writeUInt(info.minor, stream);
				writeUInt(info.revision, stream);
				
				// write the flags
				writeUInt(info.flags0, stream);
			}else if(cnst instanceof VaMthInfo) {
				final VaMthInfo info = (VaMthInfo)cnst;
				
				// write the constant's type
				writeUByte(CNST_MTHINFO, stream);
				
				// write the identifier
				writeUInt(stringMap.get(info.ident), stream);
				
				// write the flags
				writeUInt(info.flags0, stream);
				
				// write the class
				writeUInt(cnstMap.get(info.clsInfo), stream);
				
				// write the return type
				writeUInt(cnstMap.get(info.returnType), stream);
				
				// write the argument count
				writeUInt(info.getArgumentCnt(), stream);
				
				// write the arguments
				for(int j = 0; j < info.getArgumentCnt(); j++) {
					writeUInt(cnstMap.get(info.getArgument(j)), stream);
				}
			}else if(cnst instanceof VaFldInfo) {
				final VaFldInfo info = (VaFldInfo)cnst;
				
				// write the constant's type
				writeUByte(CNST_FLDINFO, stream);
				
				// write the identifier
				writeUInt(stringMap.get(info.ident), stream);
				
				// write the flags
				writeUInt(info.flags0, stream);
				
				// write the class
				writeUInt(cnstMap.get(info.clsInfo), stream);
				
				// write the type
				writeUInt(cnstMap.get(info.datatype), stream);
			}else if(cnst instanceof VaDatatype.DtPrimitive) {
				final VaDatatype.DtPrimitive type
						= (VaDatatype.DtPrimitive)cnst;
				
				// write the constant's type
				writeUByte(CNST_PRIMTYPE, stream);
				
				// write the primitive id
				writeUByte(type.identity, stream);
			}else if(cnst instanceof VaDatatype.DtArray) {
				final VaDatatype.DtArray type = (VaDatatype.DtArray)cnst;
				
				// write the constant's type
				writeUByte(CNST_ARRTYPE, stream);
				
				// write the base type and flag fields
				writeUInt(cnstMap.get(type.basetype), stream);
				writeUInt(type.flags0, stream);
			}else if(cnst instanceof VaDatatype.DtClass) {
				final VaDatatype.DtClass type = (VaDatatype.DtClass)cnst;
				
				// write the constant's type
				writeUByte(CNST_CLSTYPE, stream);
				
				// write the class info
				writeUInt(cnstMap.get(type.clsInfo), stream);
			}else if(cnst instanceof VaDatatype.DtDelegate) {
				final VaDatatype.DtDelegate type = (VaDatatype.DtDelegate)cnst;
				
				// write the constant's type
				writeUByte(CNST_DLGTYPE, stream);
				
				// write the return type
				writeUInt(cnstMap.get(type.returnType), stream);
				
				writeUInt(type.getArgumentCnt(), stream);
				
				// write the argument types
				for(int j = 0; j < type.getArgumentCnt(); j++)
					writeUInt(cnstMap.get(type.getArgument(j)), stream);
			}else if(cnst instanceof VaLiteral.LitNull) {
				// write the constant's type
				writeUByte(CNST_LIT_NULL, stream);
			}else if(cnst instanceof VaLiteral.LitUInt) {
				final VaLiteral.LitUInt literal = (VaLiteral.LitUInt)cnst;
				
				// write the constant's type
				writeUByte(CNST_UINT_LITERAL, stream);
				
				// write the value
				writeUInt(literal.value, stream);
			}else if(cnst instanceof VaLiteral.LitChar) {
				final VaLiteral.LitChar literal = (VaLiteral.LitChar)cnst;
				
				// write the constant's type
				writeUByte(CNST_CHAR_LITERAL, stream);
				
				// write the value
				writeUInt((int)literal.value, stream);
			}else if(cnst instanceof VaLiteral.LitBoolean) {
				final VaLiteral.LitBoolean literal = (VaLiteral.LitBoolean)cnst;
				
				// write the constant's type
				writeUByte(CNST_LIT_BOOLEAN, stream);
				
				// write the value
				writeUByte((byte)(literal.value ? 1 : 0), stream);
			}else if(cnst instanceof VaLiteral.LitCharArray) {
				final VaLiteral.LitCharArray literal = (VaLiteral.LitCharArray)cnst;
				
				// write the constant's type
				writeUByte(CNST_CARR_LITERAL, stream);
				
				// write the char count
				writeUInt(literal.size(), stream);
				
				// write the chars
				for(int j = 0; j < literal.size(); j++)
					writeUInt(literal.get(j), stream);
			}else{
				throw new ValibException("Unknown constant " + cnst.getClass().getSimpleName());
			}
		}
		
		// write the classes count
		writeUInt(classCnt, stream);
		
		// write the classes
		final Iterator<VaClsInfo> clsiter = image.classIterator();
		while(clsiter.hasNext()) {
			final VaClsInfo clsinfo = clsiter.next();
			final VaClass cls = image.getClass(clsinfo);
			
			// write the class info
			writeUInt(cnstMap.get(clsinfo), stream);
			
			// write the parent info
			if(cls.parentInfo != null) {
				writeUInt(cnstMap.get(cls.parentInfo), stream);
			}else{
				writeUInt(CNST_NULL, stream);
			}
			
			// write the method count
			writeUInt(cls.getMethodCnt(), stream);
			
			// write the methods
			for(int i = 0; i < cls.getMethodCnt(); i++) {
				writeUInt(cnstMap.get(cls.getMethod(i)), stream);
			}
			
			// write the field count
			writeUInt(cls.getFieldCnt(), stream);
			
			// write the fields
			for(int i = 0; i < cls.getFieldCnt(); i++) {
				writeUInt(cnstMap.get(cls.getField(i)), stream);
			}
		}
		
		// write the section count
		writeUInt(methodCnt, stream);
		
		final Iterator<VaMthInfo> mthiter = image.methodIterator();
		while(mthiter.hasNext()) {
			final VaMthInfo mthinfo = mthiter.next();
			final VaProcedure procedure = image.getMethod(mthinfo);
			
			// write the method description
			writeUInt(cnstMap.get(mthinfo), stream);
			// write the procedure 
			writeProcedure(procedure, stream);
		}
	}
	
	private void writeProcedure(final VaProcedure procedure,
			final OutputStream stream) throws ValibException {
	// write the frame count
		writeUInt(procedure.getFrameCnt(), stream);
		
		for(int j = 0; j < procedure.getFrameCnt(); j++) {
			final VaFrame frame = procedure.getFrame(j);
			
			// write the argument count
			writeUInt(frame.argumentCnt, stream);
			
			// write the register count
			writeUInt(frame.getRegisterCnt(), stream);
			
			// write the register types
			for(int k = 0; k < frame.getRegisterCnt(); k++)
				writeUInt(cnstMap.get(frame.getRegister(k)), stream);

			// write the instruction count
			writeUInt(frame.getInstCnt(), stream);
			
			// write the instructions
			for(int k = 0; k < frame.getInstCnt(); k++) {
				final VaInst opcode = frame.getInst(k);
				
				if(opcode instanceof VaInst.InstReturn) {
					final VaInst.InstReturn inst = (VaInst.InstReturn)opcode;
					writeUByte(OPCODE_RETURN, stream);
					writeUInt(inst.register, stream);
				}else if(opcode instanceof VaInst.InstLoad) {
					final VaInst.InstLoad inst = (VaInst.InstLoad)opcode;
					writeUByte(OPCODE_LOAD, stream);
					writeUInt(cnstMap.get(inst.literal), stream);
				}else if(opcode instanceof VaInst.InstInvoke) {
					final VaInst.InstInvoke inst = (VaInst.InstInvoke)opcode;
					if(inst.virtual && inst.result) {
						writeUByte(OPCODE_INVOKE_VIRT_RES, stream);
					}else if(inst.virtual && !inst.result) {
						writeUByte(OPCODE_INVOKE_VIRT_VOID, stream);
					}else if(!inst.virtual && inst.result) {
						writeUByte(OPCODE_INVOKE_STAT_RES, stream);
					}else if(!inst.virtual && !inst.result){
						writeUByte(OPCODE_INVOKE_STAT_VOID, stream);
					}
					if(inst.mthInfo == null) {
						writeUInt(0, stream);
					}else{
						writeUInt(cnstMap.get(inst.mthInfo), stream);
					}
					writeRegister(inst.object, stream);
					writeUInt(inst.getArgumentCnt(), stream);
					for(int i = 0; i < inst.getArgumentCnt(); i++)
						writeRegister(inst.getArgument(i), stream);
				}else if(opcode instanceof VaInst.InstBinOperation) {
					final VaInst.InstBinOperation inst = (VaInst.InstBinOperation)opcode;
					
					if(inst.operator == VaInst.InstBinOperation.OP_ARITH_ADD) {
						writeUByte(OPCODE_BINOP_ARITH_ADD, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_SUB) {
						writeUByte(OPCODE_BINOP_ARITH_SUB, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_MUL) {
						writeUByte(OPCODE_BINOP_ARITH_MUL, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_DIV) {
						writeUByte(OPCODE_BINOP_ARITH_DIV, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_MOD) {
						writeUByte(OPCODE_BINOP_ARITH_MOD, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_OR) {
						writeUByte(OPCODE_BINOP_ARITH_OR, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_AND) {
						writeUByte(OPCODE_BINOP_ARITH_AND, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_XOR) {
						writeUByte(OPCODE_BINOP_ARITH_XOR, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_SHL) {
						writeUByte(OPCODE_BINOP_ARITH_SHL, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_ARITH_SHR) {
						writeUByte(OPCODE_BINOP_ARITH_SHR, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_SETIF_EQUALS) {
						writeUByte(OPCODE_BINOP_SETIF_EQUALS, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_SETIF_ABOVE) {
						writeUByte(OPCODE_BINOP_SETIF_ABOVE, stream);
					}else if(inst.operator == VaInst.InstBinOperation.OP_SETIF_BELOW) {
						writeUByte(OPCODE_BINOP_SETIF_BELOW, stream);
					}else{
						throw new UnsupportedOperationException("Invalid operator for InstBinOperation");
					}
					
					writeUInt(inst.left, stream);
					writeUInt(inst.right, stream);
				}else if(opcode instanceof VaInst.InstUnOperation) {
					final VaInst.InstUnOperation inst = (VaInst.InstUnOperation)opcode;
					
					if(inst.operator == VaInst.InstUnOperation.OP_ARITH_NOT) {
						writeUByte(OPCODE_UNOP_ARITH_NOT, stream);
					}else if(inst.operator == VaInst.InstUnOperation.OP_ARITH_NEG) {
						writeUByte(OPCODE_UNOP_ARITH_NEG, stream);
					}else if(inst.operator == VaInst.InstUnOperation.OP_ARR_SIZE) {
						writeUByte(OPCODE_UNOP_ARR_SIZE, stream);
					}
					
					writeUInt(inst.operand, stream);
				}else if(opcode instanceof VaInst.InstJump) {
					final VaInst.InstJump inst = (VaInst.InstJump)opcode;
					
					writeUByte(OPCODE_JUMP, stream);
					writeUInt(inst.frameId, stream);
					writeRegister(inst.register, stream);
					
					writeUInt(inst.getArgumentCnt(), stream);
					for(int i = 0; i < inst.getArgumentCnt(); i++)
						writeRegister(inst.getArgument(i), stream);
				}else if(opcode instanceof VaInst.InstCreateObject) {
					final VaInst.InstCreateObject inst = (VaInst.InstCreateObject)opcode;
					
					writeUByte(OPCODE_CREATE_OBJECT, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
				}else if(opcode instanceof VaInst.InstCreateArray) {
					final VaInst.InstCreateArray inst = (VaInst.InstCreateArray)opcode;
					
					writeUByte(OPCODE_CREATE_ARRAY, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
					writeUInt(inst.size, stream);
				}else if(opcode instanceof VaInst.InstArrAccess) {
					final VaInst.InstArrAccess inst = (VaInst.InstArrAccess)opcode;
					
					if(inst.operator == VaInst.InstArrAccess.OP_LOAD) {
						writeUByte(OPCODE_ARRACC_LOAD, stream);
					}else if(inst.operator == VaInst.InstArrAccess.OP_STORE) {
						writeUByte(OPCODE_ARRACC_STORE, stream);
					}
					
					writeUInt(inst.array, stream);
					writeUInt(inst.index, stream);
					writeUInt(inst.operand, stream);
				}else if(opcode instanceof VaInst.InstFldAccess) {
					final VaInst.InstFldAccess inst = (VaInst.InstFldAccess)opcode;
					
					if(inst.operator == VaInst.InstFldAccess.OP_LOAD) {
						writeUByte(OPCODE_FLDACC_LOAD, stream);
					}else if(inst.operator == VaInst.InstFldAccess.OP_STORE) {
						writeUByte(OPCODE_FLDACC_STORE, stream);
					}
					
					writeUInt(cnstMap.get(inst.fldInfo), stream);
					writeUInt(inst.object, stream);
					writeUInt(inst.operand, stream);
				}else if(opcode instanceof VaInst.InstCast) {
					final VaInst.InstCast inst = (VaInst.InstCast)opcode;
					
					writeUByte(OPCODE_CAST, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
					writeUInt(inst.source, stream);
				}else if(opcode instanceof VaInst.InstInstanceof) {
					final VaInst.InstInstanceof inst = (VaInst.InstInstanceof)opcode;
					
					writeUByte(OPCODE_INSTANCEOF, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
					writeUInt(inst.object, stream);
				}else if(opcode instanceof VaInst.InstDlgConstruct) {
					final VaInst.InstDlgConstruct inst
							= (VaInst.InstDlgConstruct)opcode;
					
					writeUByte(OPCODE_DLG_CONSTRUCT, stream);
					writeUInt(cnstMap.get(inst.method), stream);
					writeUInt(inst.object, stream);
				}else if(opcode instanceof VaInst.InstCatch) {
					final VaInst.InstCatch inst = (VaInst.InstCatch)opcode;
					writeUByte(OPCODE_CATCH, stream);
					writeUInt(inst.frame, stream);
					writeUInt(inst.getArgumentCnt(), stream);
					for(int i = 0; i < inst.getArgumentCnt(); i++)
						writeUInt(inst.getArgument(i), stream);
					writeProcedure(inst.procedure, stream);
				}else if(opcode instanceof VaInst.InstThrow) {
					final VaInst.InstThrow inst = (VaInst.InstThrow)opcode;
					writeUByte(OPCODE_THROW, stream);
					writeUInt(inst.exception, stream);
				}else if(opcode instanceof VaInst.InstPass) {
					final VaInst.InstPass inst = (VaInst.InstPass)opcode;
					writeUByte(OPCODE_PASS, stream);
					writeUInt(inst.argument, stream);
					writeUInt(inst.register, stream);
				}else if(opcode instanceof VaInst.InstLeave) {
					writeUByte(OPCODE_LEAVE, stream);
				}else if(opcode instanceof VaInst.InstXNatSizeofObject) {
					final VaInst.InstXNatSizeofObject inst = (VaInst.InstXNatSizeofObject)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					writeUInt(EXT0_XNAT_SIZEOF_OBJECT, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
				}else if(opcode instanceof VaInst.InstXNatMthAddress) {
					final VaInst.InstXNatMthAddress inst = (VaInst.InstXNatMthAddress)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					writeUInt(EXT0_XNAT_METHOD_ADDRESS, stream);
					writeUInt(cnstMap.get(inst.method), stream);
				}else if(opcode instanceof VaInst.InstXNatAddressCast) {
					final VaInst.InstXNatAddressCast inst = (VaInst.InstXNatAddressCast)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					writeUInt(EXT0_XNAT_ADDRESS_CAST, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
					writeUInt(inst.address, stream);
				}else if(opcode instanceof VaInst.InstXNatAddressOf) {
					final VaInst.InstXNatAddressOf inst = (VaInst.InstXNatAddressOf)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					writeUInt(EXT0_XNAT_ADDRESS_OF, stream);
					writeUInt(inst.address, stream);
					writeUInt(inst.object, stream);
				}else if(opcode instanceof VaInst.InstXNatMakeDelegate) {
					final VaInst.InstXNatMakeDelegate inst = (VaInst.InstXNatMakeDelegate)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					writeUInt(EXT0_XNAT_MAKE_DELEGATE, stream);
					writeUInt(cnstMap.get(inst.datatype), stream);
					writeUInt(inst.method, stream);
					writeUInt(inst.object, stream);
				}else if(opcode instanceof VaInst.InstXNatRefOperation) {
					final VaInst.InstXNatRefOperation inst
							= (VaInst.InstXNatRefOperation)opcode;

					writeUByte(OPCODE_EXT0, stream);
					if(inst.operation == VaInst.InstXNatRefOperation.REF_INCREMENT) {
						writeUInt(EXT0_XNAT_REF_INCREMENT, stream);
					}else{
						writeUInt(EXT0_XNAT_REF_DECREMENT, stream);
					}
					writeUInt(inst.register, stream);
				}else if(opcode instanceof VaInst.InstXAtomCas) {
					final VaInst.InstXAtomCas inst = (VaInst.InstXAtomCas)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					if(inst.result) {
						writeUInt(EXT0_XATOM_CAS_RESULT, stream);
					}else{
						writeUInt(EXT0_XATOM_CAS_VOID, stream);
					}
					writeUInt(cnstMap.get(inst.field), stream);
					writeUInt(inst.object, stream);
					writeUInt(inst.source, stream);
					writeUInt(inst.value, stream);
				}else if(opcode instanceof VaInst.InstXPerfArrayCopy) {
					final VaInst.InstXPerfArrayCopy inst = (VaInst.InstXPerfArrayCopy)opcode;
					
					writeUByte(OPCODE_EXT0, stream);
					writeUInt(EXT0_XPERF_ARRAY_COPY, stream);
					writeUInt(inst.dest, stream);
					writeUInt(inst.source, stream);
					writeUInt(inst.destOffset, stream);
					writeUInt(inst.sourceOffset, stream);
					writeUInt(inst.size, stream);
				}else{
					throw new UnsupportedOperationException("Invalid instruction");
				}
			}
		}
	}
	
	private void analyzeClass(VaClass cls) throws ValibException {
		if(cls.parentInfo != null)
			mergeConst(cls.parentInfo);
		
		// merge the method descs
		for(int i = 0; i < cls.getMethodCnt(); i++)
			mergeConst(cls.getMethod(i));
		
		// merge the field desc
		for(int i = 0; i < cls.getFieldCnt(); i++)
			mergeConst(cls.getField(i));
	}
	
	private void analyzeSection(VaProcedure procedure) throws ValibException {
		for(int i = 0; i < procedure.getFrameCnt(); i++) {
			final VaFrame frame = procedure.getFrame(i);
			
			for(int j = 0; j < frame.getRegisterCnt(); j++)
				mergeConst(frame.getRegister(j));
			
			for(int j = 0; j < frame.getInstCnt(); j++) {
				final VaInst opcode = frame.getInst(j);
				
				if(opcode instanceof VaInst.InstLoad) {
					final VaInst.InstLoad inst = (VaInst.InstLoad)opcode;
					mergeConst(inst.literal);
				}else if(opcode instanceof VaInst.InstInvoke) {
					final VaInst.InstInvoke inst = (VaInst.InstInvoke)opcode;
					if(inst.mthInfo != null)
						mergeConst(inst.mthInfo);
				}else if(opcode instanceof VaInst.InstFldAccess) {
					final VaInst.InstFldAccess inst = (VaInst.InstFldAccess)opcode;
					mergeConst(inst.fldInfo);
				}else if(opcode instanceof VaInst.InstCreateObject) {
					final VaInst.InstCreateObject inst = (VaInst.InstCreateObject)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstCreateArray) {
					final VaInst.InstCreateArray inst = (VaInst.InstCreateArray)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstInstanceof) {
					final VaInst.InstInstanceof inst = (VaInst.InstInstanceof)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstCast) {
					final VaInst.InstCast inst = (VaInst.InstCast)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstDlgConstruct) {
					final VaInst.InstDlgConstruct inst
							= (VaInst.InstDlgConstruct)opcode;
					mergeConst(inst.method);
				}else if(opcode instanceof VaInst.InstXNatMthAddress) {
					final VaInst.InstXNatMthAddress inst
							= (VaInst.InstXNatMthAddress)opcode;
					mergeConst(inst.method);
				}else if(opcode instanceof VaInst.InstXNatSizeofObject) {
					final VaInst.InstXNatSizeofObject inst
							= (VaInst.InstXNatSizeofObject)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstXNatAddressCast) {
					final VaInst.InstXNatAddressCast inst
							= (VaInst.InstXNatAddressCast)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstXNatMakeObject) {
					final VaInst.InstXNatMakeObject inst
							= (VaInst.InstXNatMakeObject)opcode;
					mergeConst(inst.clsInfo);
				}else if(opcode instanceof VaInst.InstXNatMakeArray) {
					final VaInst.InstXNatMakeArray inst
							= (VaInst.InstXNatMakeArray)opcode;
					mergeConst(inst.datatype);
				}else if(opcode instanceof VaInst.InstXAtomCas) {
					final VaInst.InstXAtomCas inst
							= (VaInst.InstXAtomCas)opcode;
					mergeConst(inst.field);
				}else if(opcode instanceof VaInst.InstXNatMakeDelegate) {
					final VaInst.InstXNatMakeDelegate inst
							= (VaInst.InstXNatMakeDelegate)opcode;
					mergeConst(inst.datatype);
				}
			}
		}
	}
	
	// adds "cnst" and all constants that depend on "cnst" to the constant map
	private void mergeConst(Object cnst) throws ValibException {
		if(cnstMap.containsKey(cnst))
			return;
		
		// merge dependancies
		if(cnst instanceof VaClsInfo) {
			final VaClsInfo info = (VaClsInfo)cnst;
			mergeString(info.ident);
		}else if(cnst instanceof VaMthInfo) {
			final VaMthInfo info = (VaMthInfo)cnst;
			mergeString(info.ident);
			mergeConst(info.returnType);
			
			// merge the arguments
			for(int i = 0; i < info.getArgumentCnt(); i++)
				mergeConst(info.getArgument(i));
		}else if(cnst instanceof VaFldInfo) {
			final VaFldInfo info = (VaFldInfo)cnst;
			mergeString(info.ident);
			mergeConst(info.datatype);
			
			// merge the literal
			if(info.literal != null)
				mergeConst(info.literal);
		}else if(cnst instanceof VaDatatype.DtClass) {
			final VaDatatype.DtClass type = (VaDatatype.DtClass)cnst;
			mergeConst(type.clsInfo);
		}else if(cnst instanceof VaDatatype.DtArray) {
			final VaDatatype.DtArray type = (VaDatatype.DtArray)cnst;
			mergeConst(type.basetype);
		}else if(cnst instanceof VaDatatype.DtDelegate) {
			final VaDatatype.DtDelegate type = (VaDatatype.DtDelegate)cnst;
			mergeConst(type.returnType);
			for(int i = 0; i < type.getArgumentCnt(); i++)
				mergeConst(type.getArgument(i));
		}else if(cnst instanceof VaDatatype.DtPrimitive) {
		}else if(cnst instanceof VaLiteral.LitNull) {
		}else if(cnst instanceof VaLiteral.LitUInt) {
		}else if(cnst instanceof VaLiteral.LitChar) {
		}else if(cnst instanceof VaLiteral.LitBoolean) {
		}else if(cnst instanceof VaLiteral.LitCharArray) {
			// do nothing
		}else{
			throw new ValibException("Unknown constant " + cnst.getClass().getSimpleName());
		}

		// add the constant to the map
		cnstList.add(cnst);
		cnstMap.put(cnst, cnstList.size());
	}
	
	private void mergeString(final String string) {
		if(stringMap.containsKey(string))
			return;
		stringList.add(string);
		stringMap.put(string, stringList.size());
	}
	
	private static void writeRegister(int register,
			OutputStream stream) throws ValibException {
		if(register < 0xFF) {
			writeUByte((short)register, stream);
		}else{
			writeUByte((short)0xFF, stream);
			writeUInt(register, stream);
		}
	}
	
	private static void writeBool(boolean value,
			OutputStream stream) throws ValibException {
		if(value) {
			writeUByte((short)1, stream);
		}else{
			writeUByte((short)0, stream);
		}
	}
	
	// writes an unsigned integer value to a stream
	private static void writeUInt(long value,
			OutputStream stream) throws ValibException {
		writeUByte((short)((value & 0xFF000000L) >> 24), stream);
		writeUByte((short)((value & 0xFF0000L) >> 16), stream);
		writeUByte((short)((value & 0xFF00L) >> 8), stream);
		writeUByte((short)(value & 0xFFL), stream);
	}
	
	// writes an unsigned short value to a stream
	private static void writeUShort(int value,
			OutputStream stream) throws ValibException {
		writeUByte((short)((value & 0xFF00) >> 8), stream);
		writeUByte((short)(value & 0xFF), stream);
	}
	
	private static void writeUByte(short value,
			OutputStream stream) throws ValibException {
		try {
			stream.write(value);
		} catch(IOException e) {
			throw new ValibException("Could not write output file");
		}
	}
}
